home *** CD-ROM | disk | FTP | other *** search
/ Delphi 2.0 - Programmer's Utilities Power Pack / Delphi 2.0 Programmer's Utilities Power Pack.iso / m_to_r / pichrt10 / piechart.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-09-15  |  12.8 KB  |  480 lines

  1. unit PieChart;
  2.  
  3. interface
  4.  
  5. uses
  6.   SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  7.   Forms, Dialogs, StdCtrls;
  8.  
  9. // the objects on our local string list contain both the original
  10. // object and the real number which is the value of the data
  11. type
  12.   TObjectAndDouble = class(TObject)
  13.     source_object: TObject;
  14.     value: double;
  15.   end;
  16.  
  17. type
  18.   TStringListWithDouble = class(TStringList)
  19.     destructor Destroy;  override;
  20.   end;
  21.  
  22. const
  23.   min_height = 65;
  24.   min_width = 65;
  25.  
  26. type
  27.   TPieChart = class(TGraphicControl)
  28.   private
  29.     { Private declarations }
  30.     FData: TStringListWithDouble; // computed internal data
  31.     FListBox: TListBox;
  32.     FOnDblClick: TNotifyEvent;
  33.     FMouseX, FMouseY: integer;
  34.     FTotal: double;
  35.     FColour1: TColor;
  36.     FColour2: TColor;
  37.     FColour3: TColor;
  38.     FColour4: TColor;
  39.     FColour5: TColor;
  40.     FColour6: TColor;
  41.     procedure SetListBox (ListBox: TListBox);
  42.   protected
  43.     { Protected declarations }
  44.     procedure Paint;  override;
  45.     procedure DblClick;  override;
  46.     procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);  override;
  47.   public
  48.     { Public declarations }
  49.     ClickedObject: TObject;
  50.     constructor Create (AOwner: TComponent);  override;
  51.     destructor Destroy;  override;
  52.     procedure SetDataAndLabels (source_data: TStringList);
  53.     procedure SetColour1 (colour: TColor);
  54.     procedure SetColour2 (colour: TColor);
  55.     procedure SetColour3 (colour: TColor);
  56.     procedure SetColour4 (colour: TColor);
  57.     procedure SetColour5 (colour: TColor);
  58.     procedure SetColour6 (colour: TColor);
  59.     procedure Clear;
  60.   published
  61.     { Published declarations }
  62.     property Height default min_height;
  63.     property Width default min_width;
  64.     property Colour1: TColor read FColour1 write SetColour1;
  65.     property Colour2: TColor read FColour2 write SetColour2;
  66.     property Colour3: TColor read FColour3 write SetColour3;
  67.     property Colour4: TColor read FColour4 write SetColour4;
  68.     property Colour5: TColor read FColour5 write SetColour5;
  69.     property Colour6: TColor read FColour6 write SetColour6;
  70.     property Font;
  71.     property ParentFont;
  72.     property ListBox: TListBox read FListBox write SetListBox;
  73.     property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick;
  74.   end;
  75.  
  76. procedure Register;
  77.  
  78. implementation
  79.  
  80. procedure Register;
  81. begin
  82.   RegisterComponents('Davids', [TPieChart]);
  83. end;
  84.  
  85. destructor TStringListWithDouble.Destroy;
  86. var
  87.   index: integer;
  88. begin
  89.   for index := 0 to Count - 1 do
  90.     if Objects [index] <> nil then Objects [index].Free;
  91.   Inherited Destroy;
  92. end;
  93.  
  94. constructor TPieChart.Create (AOwner: TComponent);
  95. var
  96.   lst: TStringList;
  97. begin
  98.   Inherited Create (AOwner);
  99.   Width := min_width;
  100.   Height := min_height;
  101.   FData := TStringListWithDouble.Create;
  102.   FData.Sorted := False;
  103.   FData.Duplicates := dupAccept;
  104.   FListBox := nil;
  105.   FTotal := 0.0;
  106.   FColour1 := RGB ($FF, $E0, $E0);
  107.   FColour2 := RGB ($FF, $FF, $E0);
  108.   FColour3 := RGB ($E0, $FF, $E0);
  109.   FColour4 := RGB ($E0, $FF, $FF);
  110.   FColour5 := RGB ($E0, $E0, $FF);
  111.   FColour6 := RGB ($FF, $E0, $FF);
  112.   if csDesigning in ComponentState then
  113.     begin
  114.     lst := TStringList.Create;
  115.     lst.Add ('4 Smallest');
  116.     lst.Add ('6 Smaller');
  117.     lst.Add ('9 Small');
  118.     lst.Add ('11 Large');
  119.     lst.Add ('14 Larger');
  120.     lst.Add ('17 Largest');
  121.     SetDataAndLabels (lst);
  122.     lst.Free;
  123.     end;
  124. end;
  125.  
  126. destructor TPieChart.Destroy;
  127. begin
  128.   FData.Free;
  129.   Inherited Destroy;
  130. end;
  131.  
  132. procedure TPieChart.SetListBox (ListBox: TListBox);
  133. begin
  134.   FListBox := ListBox;
  135. end;
  136.  
  137. procedure TPieChart.SetColour1 (colour: TColor);
  138. begin
  139.   FColour1 := colour;
  140.   Invalidate;
  141. end;
  142.  
  143. procedure TPieChart.SetColour2 (colour: TColor);
  144. begin
  145.   FColour2 := colour;
  146.   Invalidate;
  147. end;
  148.  
  149. procedure TPieChart.SetColour3 (colour: TColor);
  150. begin
  151.   FColour3 := colour;
  152.   Invalidate;
  153. end;
  154.  
  155. procedure TPieChart.SetColour4 (colour: TColor);
  156. begin
  157.   FColour4 := colour;
  158.   Invalidate;
  159. end;
  160.  
  161. procedure TPieChart.SetColour5 (colour: TColor);
  162. begin
  163.   FColour5 := colour;
  164.   Invalidate;
  165. end;
  166.  
  167. procedure TPieChart.SetColour6 (colour: TColor);
  168. begin
  169.   FColour6 := colour;
  170.   Invalidate;
  171. end;
  172.  
  173. procedure TPieChart.Clear;
  174. begin
  175.   FData.Clear;
  176.   if FListBox <> nil
  177.     then FListBox.Clear;   // remove any items in the list box
  178.   Invalidate;
  179. end;
  180.  
  181. procedure TPieChart.SetDataAndLabels (source_data: TStringList);
  182.  
  183.   procedure QuickSort (L, R: Integer);
  184.   // sorts FData into reverse numerical order
  185.   var
  186.     I, J: integer;
  187.     X: double;
  188.   begin
  189.     I := L;
  190.     J := R;
  191.     X := TObjectAndDouble (FData.Objects [(L + R) shr 1]).Value;
  192.     repeat
  193.       while TObjectAndDouble (FData.Objects[I]).Value > X do Inc(I);
  194.       while TObjectAndDouble (FData.Objects[J]).Value < X do Dec(J);
  195.       if I <= J then
  196.       begin
  197.         FData.Exchange(I, J);
  198.         Inc(I);
  199.         Dec(J);
  200.       end;
  201.     until I > J;
  202.     if L < J then QuickSort(L, J);
  203.     if I < R then QuickSort(I, R);
  204.   end;
  205.  
  206. var
  207.   index: integer;
  208.   d: double;
  209.   s: string;
  210.   num: string;
  211.   lbl: string;
  212.   space: integer;
  213.   code: integer;
  214.   dd: TObjectAndDouble;
  215. begin
  216.   FData.Clear;
  217.   if FListBox <> nil
  218.     then FListBox.Clear;   // remove any items in the list box
  219.  
  220.   FTotal := 0.0;
  221.   for index := 0 to source_data.Count - 1 do
  222.     begin
  223.     s := Trim (source_data.Strings[index]);       // get the source string
  224.     space := Pos (' ', s);
  225.     if space = 0
  226.     then
  227.       begin
  228.       num := s;
  229.       lbl := '';                // assume no label part
  230.       end
  231.     else
  232.       begin
  233.       lbl := Trim (Copy (s, space, 999));
  234.       num := Copy (s, 1, space-1);
  235.       end;
  236.     Val (num, d, code);
  237.     if code = 0
  238.     then
  239.       begin
  240.       FTotal := FTotal + d;
  241.       dd := TObjectAndDouble.Create;
  242.       dd.value := d;
  243.       dd.source_object := source_data.Objects[index];
  244.       FData.AddObject (s, dd);
  245.       end
  246.     else
  247.       dd := nil; // should we raise an error here?
  248.     end;
  249.   if FData.Count <> 0 then
  250.     begin
  251.     QuickSort (0, FData.Count - 1);
  252.     if FListBox <> nil then
  253.       // copy the user's strings and objects to the list box
  254.       for index := 0 to FData.Count - 1 do
  255.          FListBox.Items.AddObject (
  256.              FData.strings[index],
  257.              TObjectAndDouble (Fdata.objects[index]).source_object);
  258.     end;
  259.   Invalidate;
  260. end;
  261.  
  262. procedure TPieChart.MouseDown (Button: TMouseButton; Shift: TShiftState;
  263.                                X, Y: Integer);
  264. begin
  265.   {record the mouse co-ordinates in case of a double-click}
  266.   FMouseX := X;
  267.   FMouseY := Y;
  268. end;
  269.  
  270. procedure TPieChart.DblClick;
  271.  
  272.   function atan2 (y, x: double): double;
  273.   var
  274.      a: double;
  275.   begin
  276.     if x = 0.0
  277.       then
  278.         if y < 0.0
  279.           then atan2 := -pi / 2 else atan2 := pi / 2
  280.       else
  281.         if y = 0.0
  282.           then
  283.             if x < 0.0
  284.               then atan2 := pi else atan2 := 0.0
  285.           else
  286.             begin
  287.             a := arctan (abs (y/x));
  288.             if x > 0.0
  289.               then
  290.                 if y > 0.0
  291.                   then atan2 := a else atan2 := -a
  292.             else
  293.                 if y > 0.0
  294.                   then atan2 := pi - a else atan2 := -(pi - a)
  295.             end;
  296.   end;
  297.  
  298. var
  299.    found: boolean;
  300.    desired: integer;
  301.    x, y: integer;
  302.    dx, dy, dr: double;
  303.    pie_radius: double;
  304.    index: integer;
  305.    test_theta, theta, d_theta, next_theta: double;
  306.    d: double;
  307. begin
  308.   Inherited Click;
  309.   if Assigned (FOnDblClick) then
  310.     begin
  311.     {find out where we were clicked - in client co-ordinates}
  312.     {translate this relative to the centre of the pie chart}
  313.     dx := FMouseX - Width div 2;
  314.     dy := Height div 2 - FMouseY;
  315.     dr := sqrt (sqr (dx) + sqr (dy));
  316.     pie_radius := Width div 2;
  317.     if Height > Width then pie_radius := Height;
  318.  
  319.     if (dr < pie_radius) and (FData.Count <> 0) then
  320.       begin
  321.       theta := atan2 (dy, dx);
  322.       if theta < 0.0 then theta := theta +  2.0 * pi;
  323.       test_theta := 0.0;
  324.       found := false;
  325.       index := FData.Count - 1;
  326.       index := 0;
  327.       while (not found) and (index < FData.Count) do
  328.         begin
  329.         d := TObjectAndDouble (FData.Objects [index]).Value;
  330.         d_theta := (2.0 * pi * d) / FTotal;
  331.         next_theta := test_theta + d_theta;
  332.         found := (theta > test_theta) and (theta < next_theta);
  333.         if found
  334.           then desired := index
  335.           else
  336.           begin
  337.           test_theta := next_theta;
  338.           Inc (index);
  339.           end;
  340.         end;
  341.       if found then
  342.         begin
  343.         ClickedObject := TObjectAndDouble (FData.Objects [index]).source_object;
  344.         FOnDblClick (Self);
  345.         end;
  346.       end;
  347.     end;
  348. end;
  349.  
  350. procedure TPieChart.Paint;
  351. const
  352.   radius = 1000;    {nominal radius just for line edges}
  353. var
  354.   colour_number: byte;
  355.   theta, next_theta, d_theta: double;
  356.   x0, y0: integer;
  357.   x, y: integer;
  358.   x1, y1: integer;
  359.  
  360.   procedure draw_label (const s: string);
  361.   var
  362.     pie_radius: integer;
  363.     semi_width, semi_height: integer;
  364.     x_mid, y_mid, x1, x2, y1, y2: integer;
  365.     mid_theta: double;
  366.     max_radius: double;
  367.     text_radius: double;
  368.     OldBkMode: integer;
  369.   begin
  370.     if (d_theta > 0.13) and (length (s) <> 0) then
  371.       begin
  372.       OldBkMode := SetBkMode(Canvas.Handle, TRANSPARENT);
  373.       if Width < Height
  374.         then pie_radius := Width div 2
  375.         else pie_radius := Height div 2;
  376.       semi_width := Canvas.TextWidth (s) div 2;
  377.       semi_height := Canvas.TextHeight (s) div 2;
  378.       mid_theta := (theta + next_theta) / 2.0;
  379.       {compute the central point, if it was on the rim}
  380.       x_mid := x0 + round (pie_radius * cos (mid_theta));
  381.       y_mid := y0 - round (pie_radius * sin (mid_theta));
  382.       {compute the bounding rectangle}
  383.       x1 := x_mid - semi_width;  x2 := x_mid + semi_width;
  384.       y1 := y_mid - semi_height;  y2 := y_mid + semi_height;
  385.       {find the maximum radius from the centre to the four corners of the bounding rectangle}
  386.       max_radius := 0.0;
  387.       text_radius := round (sqrt (sqr (x1 - x0) + sqr (y1 - y0)));
  388.       if text_radius > max_radius then max_radius := text_radius;
  389.       text_radius := round (sqrt (sqr (x2 - x0) + sqr (y1 - y0)));
  390.       if text_radius > max_radius then max_radius := text_radius;
  391.       text_radius := round (sqrt (sqr (x1 - x0) + sqr (y2 - y0)));
  392.       if text_radius > max_radius then max_radius := text_radius;
  393.       text_radius := round (sqrt (sqr (x2 - x0) + sqr (y2 - y0)));
  394.       if text_radius > max_radius then max_radius := text_radius;
  395.       {compute the text radius that will just fit inside the circle}
  396.       text_radius := 2.0 * pie_radius - max_radius;
  397.       x_mid := x0 + round (text_radius * cos (mid_theta));
  398.       y_mid := y0 - round (text_radius * sin (mid_theta));
  399.       Canvas.TextOut (x_mid - semi_width, y_mid - semi_height, s);
  400.       SetBkMode(Canvas.Handle, OldBkMode);
  401.       end;
  402.   end;
  403.  
  404.   procedure draw_pie_segment;
  405.   const
  406.     num_colours = 6;
  407.   begin
  408.     if (x <> x1) or (y <> y1) or (d_theta > 0.15) then
  409.       begin
  410.       case colour_number of
  411.         0: Canvas.Brush.Color := FColour1;
  412.         1: Canvas.Brush.Color := FColour2;
  413.         2: Canvas.Brush.Color := FColour3;
  414.         3: Canvas.Brush.Color := FColour4;
  415.         4: Canvas.Brush.Color := FColour5;
  416.         5: Canvas.Brush.Color := FColour6;
  417.       end;
  418.       Inc (colour_number);
  419.       colour_number := colour_number mod num_colours;
  420.       Canvas.Pie (0, 0, Width, Height, x, y, x1, y1);
  421.       end;
  422.   end;
  423.  
  424.   procedure compute_segment (delta: double;  s: string;  do_pie: boolean);
  425.   const
  426.     num_colours = 6;
  427.   begin
  428.     d_theta := (2.0 * pi * delta) / FTotal;
  429.     next_theta := theta + d_theta;
  430.     x1 := x0 + round (radius * cos (next_theta));
  431.     y1 := y0 - round (radius * sin (next_theta));
  432.     if do_pie
  433.       then draw_pie_segment
  434.       else draw_label (s);
  435.     theta := next_theta;
  436.     x := x1;
  437.     y := y1;
  438.   end;
  439.  
  440. var
  441.   d: double;
  442.   index: integer;
  443.   s: string;
  444.   space: integer;
  445. begin
  446.   x0 := Width div 2;
  447.   y0 := Height div 2;
  448.   Canvas.Pen.Color := clBlack;
  449.   if FTotal > 0.0 then
  450.     begin
  451.     colour_number := 0;
  452.     x := x0 + radius;
  453.     y := y0;
  454.     theta := 0.0;
  455.     for index := 0 to FData.Count - 1 do
  456.       begin
  457.       d := TObjectAndDouble (FData.Objects [index]).Value;
  458.       compute_segment (d, '', true);
  459.       end;
  460.     x := x0 + radius;
  461.     y := y0;
  462.     theta := 0.0;
  463.     Canvas.Font := Self.Font;
  464.     Canvas.Font.Color := clBlack;
  465.     for index := 0 to FData.Count - 1 do
  466.       begin
  467.       d := TObjectAndDouble (FData.Objects [index]).Value;
  468.       s := Trim (FData.Strings [index]);
  469.       space := Pos (' ', s);
  470.       if space = 0
  471.       then s := ''
  472.       else s := Trim (Copy (s, space, 999));
  473.       compute_segment (d, s, false);
  474.       end;
  475.     end;
  476. end;
  477.  
  478. end.
  479.  
  480.